{
 "metadata": {
  "signature": "sha256:7a4d9fe2d6a5f162cbc0e9d9f7f779e03d3ef36d7536504816c4941ad6094fad"
 },
 "nbformat": 3,
 "nbformat_minor": 0,
 "worksheets": [
  {
   "cells": [
    {
     "cell_type": "code",
     "collapsed": false,
     "id": "48C6CC91303241579CDBF07BB4A1998A",
     "input": [
      "import pandas as pd\n",
      "import numpy as np\n",
      "from datetime import datetime as dt\n",
      "import matplotlib.pyplot as plt\n",
      "from CAL.PyCAL import font\n",
      "from scipy.optimize import minimize\n",
      "import seaborn as sns\n",
      "sns.set_style('whitegrid')\n",
      "\n",
      "\n",
      "def get_smart_weight(cov_mat, method='min variance', wts_adjusted=False):\n",
      "    '''\n",
      "    \u529f\u80fd\uff1a\u8f93\u5165\u534f\u65b9\u5dee\u77e9\u9635\uff0c\u5f97\u5230\u4e0d\u540c\u4f18\u5316\u65b9\u6cd5\u4e0b\u7684\u6743\u91cd\u914d\u7f6e\n",
      "    \u8f93\u5165\uff1a\n",
      "        cov_mat  pd.DataFrame,\u534f\u65b9\u5dee\u77e9\u9635\uff0cindex\u548ccolumn\u5747\u4e3a\u8d44\u4ea7\u540d\u79f0\n",
      "        method  \u4f18\u5316\u65b9\u6cd5\uff0c\u53ef\u9009\u7684\u6709min variance\u3001risk parity\u3001max diversification\u3001equal weight\n",
      "    \u8f93\u51fa\uff1a\n",
      "        pd.Series  index\u4e3a\u8d44\u4ea7\u540d\uff0cvalues\u4e3aweight\n",
      "    PS:\n",
      "        \u4f9d\u8d56scipy package\n",
      "    '''\n",
      "    \n",
      "    if not isinstance(cov_mat, pd.DataFrame):\n",
      "        raise ValueError('cov_mat should be pandas DataFrame\uff01')\n",
      "        \n",
      "    omega = np.matrix(cov_mat.values)  # \u534f\u65b9\u5dee\u77e9\u9635\n",
      "    \n",
      "    # \u5b9a\u4e49\u76ee\u6807\u51fd\u6570\n",
      "    def fun1(x):\n",
      "        return np.matrix(x) * omega * np.matrix(x).T\n",
      "    \n",
      "    def fun2(x):\n",
      "        tmp = (omega * np.matrix(x).T).A1\n",
      "        risk = x * tmp\n",
      "        delta_risk = [sum((i - risk)**2) for i in risk]\n",
      "        return sum(delta_risk)\n",
      "    \n",
      "    def fun3(x):\n",
      "        den = x * omega.diagonal().T\n",
      "        num = np.sqrt(np.matrix(x) * omega * np.matrix(x).T)\n",
      "        return num/den\n",
      "    \n",
      "    # \u521d\u59cb\u503c + \u7ea6\u675f\u6761\u4ef6 \n",
      "    x0 = np.ones(omega.shape[0]) / omega.shape[0]  \n",
      "    bnds = tuple((0,None) for x in x0)\n",
      "    cons = ({'type':'eq', 'fun': lambda x: sum(x) - 1})\n",
      "    options={'disp':False, 'maxiter':1000, 'ftol':1e-20}\n",
      "        \n",
      "    if method == 'min variance':   \n",
      "        res = minimize(fun1, x0, bounds=bnds, constraints=cons, method='SLSQP', options=options) \n",
      "    elif method == 'risk parity':\n",
      "        res = minimize(fun2, x0, bounds=bnds, constraints=cons, method='SLSQP', options=options)\n",
      "    elif method == 'max diversification':\n",
      "        res = minimize(fun3, x0, bounds=bnds, constraints=cons, method='SLSQP', options=options)\n",
      "    elif method == 'equal weight':\n",
      "        return pd.Series(index=cov_mat.index, data=1.0 / cov_mat.shape[0])\n",
      "    else:\n",
      "        raise ValueError('method should be min variance/risk parity/max diversification/equal weight\uff01\uff01\uff01')\n",
      "        \n",
      "    # \u6743\u91cd\u8c03\u6574\n",
      "    if res['success'] == False:\n",
      "        # print res['message']\n",
      "        pass\n",
      "    wts = pd.Series(index=cov_mat.index, data=res['x'])\n",
      "    if wts_adjusted == True:\n",
      "        wts = wts[wts >= 0.0001]\n",
      "        return wts / wts.sum() * 1.0\n",
      "    elif wts_adjusted == False:\n",
      "        return wts\n",
      "    else:\n",
      "        raise ValueError('wts_adjusted should be True/False\uff01')\n",
      "        \n",
      "def get_dates(start_date, end_date, frequency='daily'):\n",
      "    '''\n",
      "    \u529f\u80fd\uff1a\u8f93\u5165\u8d77\u59cb\u65e5\u671f\u548c\u9891\u7387\uff0c\u5373\u53ef\u83b7\u5f97\u65e5\u671f\u5217\u8868\uff08daily\u5305\u62ec\u8d77\u59cb\u65e5\uff0c\u5176\u4f59\u7684\u90fd\u662f\u4f4d\u4e8e\u8d77\u59cb\u65e5\u4e2d\u95f4\u7684\uff09\n",
      "    \u8f93\u5165\u53c2\u6570\uff1a\n",
      "       start_date\uff0c\u5f00\u59cb\u65e5\u671f\uff0c'xxxxxxxx'\u5f62\u5f0f\n",
      "       end_date\uff0c\u622a\u6b62\u65e5\u671f\uff0c'xxxxxxxx'\u5f62\u5f0f\n",
      "       frequency\uff0c\u9891\u7387\uff0cdaily\u4e3a\u6240\u6709\u4ea4\u6613\u65e5\uff0cdaily1\u4e3a\u6240\u6709\u81ea\u7136\u65e5\uff0cweekly\u4e3a\u6bcf\u5468\u6700\u540e\u4e00\u4e2a\u4ea4\u6613\u65e5\uff0cweekly2\u4e3a\u6bcf\u9694\u4e24\u5468\uff0cmonthly\u4e3a\u6bcf\u6708\u6700\u540e\u4e00\u4e2a\u4ea4\u6613\u65e5\uff0cquarterly\u4e3a\u6bcf\u5b63\u6700\u540e\u4e00\u4e2a\u4ea4\u6613\u65e5\n",
      "    \u8f93\u51fa\u53c2\u6570\uff1a\n",
      "       \u83b7\u5f97list\u578b\u65e5\u671f\u5217\u8868\uff0c\u4ee5'xxxxxxxx'\u5f62\u5f0f\u5b58\u50a8\n",
      "    PS:\n",
      "        \u8981\u7528\u5230DataAPI.TradeCalGet\uff01\uff01\uff01\n",
      "    '''\n",
      "    \n",
      "    data = DataAPI.TradeCalGet(exchangeCD=u\"XSHG\",beginDate=start_date,endDate=end_date,field=u\"calendarDate,isOpen,isWeekEnd,isMonthEnd,isQuarterEnd\",pandas=\"1\")\n",
      "    if frequency == 'daily':\n",
      "        data = data[data['isOpen'] == 1]\n",
      "    elif frequency == 'daily1':\n",
      "        pass\n",
      "    elif frequency == 'weekly':\n",
      "        data = data[data['isWeekEnd'] == 1]\n",
      "    elif frequency == 'weekly2':\n",
      "        data = data[data['isWeekEnd'] == 1]\n",
      "        data = data[0:data.shape[0]:2]\n",
      "    elif frequency == 'monthly':\n",
      "        data = data[data['isMonthEnd'] == 1]\n",
      "    elif frequency == 'quarterly':\n",
      "        data = data[data['isQuarterEnd'] == 1]\n",
      "    else:\n",
      "        raise ValueError('\u8c03\u4ed3\u9891\u7387\u5fc5\u987b\u4e3adaily/daily1/weekly/weekly2/monthly/quarterly\uff01\uff01\uff01')\n",
      "    # date_list = map(lambda x: x[0:4]+x[5:7]+x[8:10], data['calendarDate'].values.tolist())\n",
      "    date_list = data['calendarDate'].values.tolist()\n",
      "    return date_list\n",
      "\n",
      "\n",
      "def shift_date(date, n, direction='back'): \n",
      "    '''\n",
      "    \u529f\u80fd\uff1a\u7ed9\u5b9adate\uff0c\u83b7\u53d6\u8be5\u65e5\u671f\u524d/\u540en\u4e2a\u4ea4\u6613\u65e5\u5bf9\u5e94\u7684\u4ea4\u6613\u65e5\n",
      "    \u8f93\u5165\uff1a\n",
      "        date  'yyyymmdd'\u7c7b\u578b\u5b57\u7b26\u4e32\n",
      "        n  \u975e\u8d1f\u6574\u6570\uff0c\u53d6\u503c\u533a\u95f4\uff080,720\uff09\n",
      "        direction  \u65b9\u5411\uff0c\u53d6\u503c\u4e3aback/forward\n",
      "    PS\uff1a\n",
      "        get_dates()\n",
      "    '''\n",
      "    \n",
      "    last_two_year = str(int(date[:4])-3) + '0101'\n",
      "    forward_two_year = str(int(date[:4])+3) + '1231'\n",
      "    if direction == 'back':\n",
      "        date_list = get_dates(last_two_year, date, 'daily')\n",
      "        return date_list[len(date_list)-1-n]\n",
      "    elif direction == 'forward':\n",
      "        date_list = get_dates(date, forward_two_year, 'daily')\n",
      "        return date_list[n]\n",
      "    else:\n",
      "        raise ValueError('direction should be back/forward\uff01\uff01\uff01')\n",
      "\n",
      "\n",
      "def cal_maxdrawdown(data):\n",
      "    '''\n",
      "    \u529f\u80fd\uff1a\u7ed9\u5b9a\u51c0\u503c\u6570\u636e\uff08list, np.array, pd.Series, pd.DataFrame\uff09\uff0c\u8fd4\u56de\u6700\u5927\u56de\u64a4\n",
      "    \u8f93\u5165\uff1a\n",
      "        data, list/np.array/pd.Series/pd.DataFrame\uff0c\u51c0\u503c\u66f2\u7ebf\uff0c\u521d\u59cb\u91d1\u4e3a1\n",
      "    \u8f93\u51fa\uff1a\n",
      "        list/np.array/pd.Series\u8fd4\u56defloat\n",
      "        pd.DataFrame\u8fd4\u56depd.DataFrame\uff0cindex\u4e3aDataFrame.columns\n",
      "    '''\n",
      "    \n",
      "    if isinstance(data, list):\n",
      "        data = np.array(data)\n",
      "    if isinstance(data, pd.Series):\n",
      "        data = data.values\n",
      "        \n",
      "    def get_mdd(values): # values\u4e3anp.array\u7684\u51c0\u503c\u66f2\u7ebf\uff0c\u521d\u59cb\u8d44\u91d1\u4e3a1\n",
      "        dd = [values[i:].min() / values[i] - 1 for i in range(len(values))]\n",
      "        return abs(min(dd))\n",
      "    \n",
      "    if not isinstance(data, pd.DataFrame):\n",
      "        return get_mdd(data)\n",
      "    else:\n",
      "        return data.apply(get_mdd)\n",
      "    \n",
      "    \n",
      "def cal_indicators(df_daily_return):\n",
      "    '''\n",
      "    \u529f\u80fd\uff1a\u7ed9\u5b9adaily return\uff0c\u8ba1\u7b97\u5404\u7ec4\u5408\u7684\u8bc4\u4ef7\u6307\u6807\uff0c\u5305\u62ec\uff1a\u5e74\u5316\u6536\u76ca\u7387\u3001\u5e74\u5316\u6807\u51c6\u5dee\u3001\u590f\u666e\u503c\u3001\u6700\u5927\u56de\u64a4\n",
      "    \u8f93\u5165\uff1a\n",
      "        df_daily_return  pd.DataFrame\uff0cindex\u4e3a\u5347\u5e8f\u6392\u5217\u7684\u65e5\u671f\uff0ccolumns\u4e3a\u5404\u7ec4\u5408\u540d\u79f0\uff0cvalue\u4e3adaily_return\n",
      "    '''\n",
      "    \n",
      "    df_cum_value = (df_daily_return + 1).cumprod()\n",
      "    res = pd.DataFrame(index=['\u5e74\u5316\u6536\u76ca\u7387','\u5e74\u5316\u6807\u51c6\u5dee','\u590f\u666e\u503c','\u6700\u5927\u56de\u64a4'], columns=df_daily_return.columns, data=0.0)\n",
      "    res.loc['\u5e74\u5316\u6536\u76ca\u7387'] = (df_daily_return.mean() * 250).apply(lambda x: '%.2f%%' % (x*100))\n",
      "    res.loc['\u5e74\u5316\u6807\u51c6\u5dee'] = (df_daily_return.std() * np.sqrt(250)).apply(lambda x: '%.2f%%' % (x*100))\n",
      "    res.loc['\u590f\u666e\u503c'] = (df_daily_return.mean() / df_daily_return.std() * np.sqrt(250)).apply(lambda x: np.round(x, 2))\n",
      "    res.loc['\u6700\u5927\u56de\u64a4'] = cal_maxdrawdown(df_cum_value).apply(lambda x: '%.2f%%' % (x*100))\n",
      "    return res\n"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "id": "80C40E8F42B04C0DA45CCEFBC5301629",
     "input": [
      "start_date = '20130913'\n",
      "end_date = '20171215'\n",
      "\n",
      "data = DataAPI.TradeCalGet(exchangeCD=u\"XSHG\",beginDate=start_date,endDate=end_date,field=u\"calendarDate,isOpen,isWeekEnd,isMonthEnd,isQuarterEnd\",pandas=\"1\")\n",
      "\n",
      "start_date = '2007-01-01'\n",
      "end_date = '2016-11-02'\n",
      "idx = ['000300', '399005', 'HSI', 'SPX', '000012']  # \u6caa\u6df1300\u3001\u4e2d\u5c0f\u677f\u6307\u3001\u6052\u751f\u6307\u6570\u3001\u6807\u666e500\u3001\u56fd\u503a\n",
      "data1 = DataAPI.MktIdxdGet(ticker=idx, beginDate=start_date, endDate=end_date, field=u\"secShortName,tradeDate,CHGPct\", pandas=\"1\")\n",
      "total_daily_return = data1.pivot(index='tradeDate', columns='secShortName', values='CHGPct').dropna(how='all').fillna(0.0)\n",
      "# total_daily_return.index = map(lambda x: x.replace('-',''), total_daily_return.index)\n",
      "total_daily_return.head()"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "id": "B844DF102DFB462C8665995EA6DD2F13",
     "input": [
      "# backtest\n",
      "selected_daily_return = total_daily_return[['\u6caa\u6df1300', '\u4e2d\u5c0f\u677f\u6307', '\u6052\u751f\u6307\u6570', '\u6807\u666e500', '\u4e0a\u8bc1\u56fd\u503a']]\n",
      "Ndays = 500   # \u7528\u591a\u5c11\u5929\u4f30\u7b97\u534f\u65b9\u5dee\u77e9\u9635\n",
      "starts = shift_date(selected_daily_return.index[0], Ndays, 'forward')\n",
      "ends = selected_daily_return.index[-1]\n",
      "portfolio_cum_value = pd.DataFrame(index=selected_daily_return.loc[starts:ends].index, columns=['min variance', 'risk parity', 'max diversification','equal weight'], data=0.0)  # \u8bb0\u5f55\u7ec4\u5408\u7d2f\u8ba1\u51c0\u503c\n",
      "portfolio_positions = {}  # \u8bb0\u5f55\u7ec4\u5408\u5404\u8d44\u4ea7\u6301\u4ed3 \n",
      "allocation_methods = {'min variance', 'risk parity', 'max diversification','equal weight'}\n",
      "for k in allocation_methods:\n",
      "    portfolio_positions[k] = pd.DataFrame(index=portfolio_cum_value.index, columns=selected_daily_return.columns, data=0.0)\n",
      "date_list = sorted(get_dates(starts, ends, 'quarterly')+[starts, ends])\n",
      "for i in range(len(date_list)-1):\n",
      "    current_period = date_list[i]\n",
      "    next_period = date_list[i+1]\n",
      "    tmp_date = shift_date(current_period, Ndays)\n",
      "    cov_mat = selected_daily_return.loc[tmp_date:current_period].cov()*250\n",
      "    # \u6743\u91cd\u4f18\u5316\n",
      "    for j in allocation_methods:\n",
      "        wts = get_smart_weight(cov_mat, method=j, wts_adjusted=False)\n",
      "        daily_rtn = selected_daily_return.loc[current_period:next_period]\n",
      "        daily_rtn.ix[0] = 0.0\n",
      "        assets_positions = (daily_rtn + 1).cumprod() * wts\n",
      "        portfolio_positions[j].loc[assets_positions.index,:] = (assets_positions.T / assets_positions.sum(axis=1)).T\n",
      "        cum_value = assets_positions.sum(axis=1)\n",
      "        if i == 0:\n",
      "            portfolio_cum_value.loc[cum_value.index, j] = cum_value * 1.0\n",
      "        else:\n",
      "            portfolio_cum_value.loc[cum_value.index, j] = cum_value * portfolio_cum_value.loc[cum_value.index[0],j]\n",
      "            \n",
      "assets_cum_value = (total_daily_return.loc[starts:ends] + 1).cumprod()\n",
      "assets_cum_value['date'] = map(lambda x: dt.strptime(x, '%Y-%m-%d'), assets_cum_value.index)\n",
      "\n",
      "fig = plt.figure(figsize=(15,6))\n",
      "ax = fig.add_subplot(111)\n",
      "font.set_size(12)\n",
      "colors = ['royalblue','orange','powderblue','sage','gray','dimgray']\n",
      "columns = ['\u6caa\u6df1300', '\u4e2d\u5c0f\u677f\u6307', '\u6052\u751f\u6307\u6570', '\u6807\u666e500', '\u4e0a\u8bc1\u56fd\u503a']\n",
      "for i in range(len(columns)):\n",
      "    ax.plot(assets_cum_value['date'].values, assets_cum_value[columns[i]].values, color=colors[i])\n",
      "ax.plot(assets_cum_value['date'].values, portfolio_cum_value['risk parity'].values, color='r')\n",
      "ax.legend([i.decode('utf8') for i in columns] + ['risk parity'], prop=font, loc='best')\n",
      "ax.xaxis.set_tick_params(labelsize=12)\n",
      "ax.yaxis.set_tick_params(labelsize=12)\n",
      "ax.grid(True)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "id": "1E4F6690A4B34909819DF21D81F7CE05",
     "input": [
      "assets_indicators = cal_indicators(selected_daily_return.loc[starts:ends])\n",
      "portfolio_indicators = cal_indicators(portfolio_cum_value.pct_change().dropna())\n",
      "assets_indicators['risk parity'] = portfolio_indicators['risk parity']\n",
      "assets_indicators"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    }
   ],
   "metadata": {}
  }
 ]
}